package com.tbynet.jwp.plugin.aop;

import com.jfinal.aop.AopManager;
import com.jfinal.kit.StrKit;
import com.jfinal.log.Log;
import com.jfinal.plugin.IPlugin;
import com.tbynet.jwp.framework.aop.Aop;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.*;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

@Deprecated
public class AopMappingPlugin implements IPlugin {

    // 存放已被扫描过的 controller，避免被多次扫描
    private static final Set<Class<?>> scannedAop = new HashSet<>();

    // 过滤被扫描的资源
    private static Predicate<URL> resourceFilter = null;

    // 扫描的基础 package，只扫描该包及其子包之下的类
    private String basePackage;

    // 跳过不需要被扫描的类
    private Predicate<String> classSkip;

    // 调用 aopManager.add(...) 添加扫描结果
    private AopManager aopManager = AopManager.me();

    private ClassLoader classLoader;

    public AopMappingPlugin(String basePackage, Predicate<String> classSkip) {
        if (StrKit.isBlank(basePackage)) {
            throw new IllegalArgumentException("basePackage can not be blank");
        }

        String bp = basePackage.replace('.', '/');
        bp = bp.endsWith("/") ? bp : bp + '/';				// 添加后缀字符 '/'
        bp = bp.startsWith("/") ? bp.substring(1) : bp;		// 删除前缀字符 '/'

        this.basePackage = bp;
        this.classSkip = classSkip;
    }

    public AopMappingPlugin(String basePackage) {
        this(basePackage, null);
    }

    @Override
    public boolean start() {
        Log.getLog(AopMappingPlugin.class).info("AopMappingPlugin  开始启动...");
        try {
            classLoader = getClassLoader();
            List<URL> urlList = getResources();
            scanResources(urlList);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        Log.getLog(AopMappingPlugin.class).info("AopMappingPlugin 启动完成，共扫描 " + scannedAop.size() + " 个类");
        return true;
    }

    @Override
    public boolean stop() {
        scannedAop.clear(); // 清理缓存，以便重新启动
        Log.getLog(AopMappingPlugin.class).info("AopMappingPlugin 已停止");
        return true;
    }

    private ClassLoader getClassLoader() {
        ClassLoader ret = Thread.currentThread().getContextClassLoader();
        return ret != null ? ret : AopMappingPlugin.class.getClassLoader();
    }

    private List<URL> getResources() throws IOException {
        List<URL> ret = new ArrayList<>();

        // 用于去除重复
        Set<String> urlSet = new HashSet<>();
        // ClassLoader.getResources(...) 参数只支持包路径分隔符为 '/'，而不支持 '\'
        Enumeration<URL> urls = classLoader.getResources(basePackage);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();

            // 过滤不需要扫描的资源
            if (resourceFilter != null && !resourceFilter.test(url)) {
                continue ;
            }

            String urlStr = url.toString();
            if ( ! urlSet.contains(urlStr) ) {
                urlSet.add(urlStr);
                ret.add(url);
            }
        }
        return ret;
    }

    private void scanResources(List<URL> urlList) throws IOException {
        for (URL url : urlList) {
            String protocol = url.getProtocol();
            if ("jar".equals(protocol)) {
                scanJar(url);
            } else if ("file".equals(protocol)) {
                scanFile(url);
            }
        }
    }

    private void scanJar(URL url) throws IOException {
        URLConnection urlConn = url.openConnection();
        if (urlConn instanceof JarURLConnection) {
            JarURLConnection jarUrlConn = (JarURLConnection)urlConn;
            try (JarFile jarFile = jarUrlConn.getJarFile()) {
                Enumeration<JarEntry> jarFileEntries = jarFile.entries();
                while (jarFileEntries.hasMoreElements()) {
                    JarEntry jarEntry = jarFileEntries.nextElement();
                    String en = jarEntry.getName();
                    // 只扫描 basePackage 之下的类
                    if (en.endsWith(".class") && en.startsWith(basePackage)) {
                        // JarEntry.getName() 返回值中的路径分隔符在所有操作系统下都是 '/'
                        en = en.substring(0, en.length() - 6).replace('/', '.');
                        scanAop(en);
                    }
                }
            }
        }
    }

    private void scanFile(URL url) {
        String path = url.getPath();
        path = decodeUrl(path);
        File file = new File(path);
        String classPath = getClassPath(file);
        scanFile(file, classPath);
    }

    private void scanFile(File file, String classPath) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File fi : files) {
                    scanFile(fi, classPath);
                }
            }
        }
        else if (file.isFile()) {
            String fullName = file.getAbsolutePath();
            if (fullName != null && fullName.endsWith(".class")) {
                String className = fullName.substring(classPath.length(), fullName.length() - 6).replace(File.separatorChar, '.');
                scanAop(className);
            }
        }
    }

    private String getClassPath(File file) {
        String ret = file.getAbsolutePath();
        // 添加后缀，以便后续的 indexOf(bp) 可以正确获得下标值，因为 bp 确定有后缀
        if ( ! ret.endsWith(File.separator) ) {
            ret = ret + File.separator;
        }

        // 将 basePackage 中的路径分隔字符转换成与 OS 相同，方便处理路径
        String bp = basePackage.replace('/', File.separatorChar);
        int index = ret.lastIndexOf(bp);
        if (index != -1) {
            ret = ret.substring(0, index);
        }

        return ret;
    }

    @SuppressWarnings("unchecked")
    private void scanAop(String className) {
        // 跳过不需要被扫描的 className
        if (classSkip != null && classSkip.test(className)) {
            return ;
        }

        Class<?> c = loadClass(className);
        if (c != null && !scannedAop.contains(c)) {
            // 确保 class 只被扫描一次
            scannedAop.add(c);

            int mod = c.getModifiers();
            if (Modifier.isPublic(mod) && ! Modifier.isAbstract(mod) && ! Modifier.isInterface(mod)) {
                Aop aop = c.getAnnotation(Aop.class);
                if (aop != null) {
                    Class<?> targetClass = aop.value();

                    // 处理 Void.class 默认值
                    if (targetClass == Void.class) {
                        targetClass = findTargetClass(c);
                    }

                    // 验证目标类有效性
                    if (isValidTargetClass(targetClass, c)) {
                        Log.getLog(AopMappingPlugin.class).info("注册 AOP 映射:  " + targetClass.getName() + " -> " + className);
                        aopManager.addMapping((Class<Object>) targetClass, c);
                    } else {
                        Log.getLog(AopMappingPlugin.class).warn("Invalid Aop mapping for class: " + className);
                    }
                }
            }
        }
    }

    /**
     * 自动查找目标类（接口）
     */
    private Class<?> findTargetClass(Class<?> implementationClass) {
        // 优先查找接口
        Class<?>[] interfaces = implementationClass.getInterfaces();
        if (interfaces.length == 1) {
            return interfaces[0];
        } else if (interfaces.length > 1) {
            // 如果有多个接口，查找名字匹配的（约定：Service 接口以 Service 结尾）
            for (Class<?> iface : interfaces) {
                if (iface.getSimpleName().endsWith("Service")) {
                    return iface;
                }
            }
            // 返回第一个接口
            return interfaces[0];
        }

        // 如果没有接口，查找父类
        Class<?> superClass = implementationClass.getSuperclass();
        if (superClass != null && superClass != Object.class) {
            return superClass;
        }

        // 最后使用实现类本身
        return implementationClass;
    }

    /**
     * 验证目标类是否有效
     */
    private boolean isValidTargetClass(Class<?> targetClass, Class<?> implementationClass) {
        if (targetClass == Void.class) {
            return false;
        }

        // 检查实现关系
        if (targetClass.isInterface()) {
            return targetClass.isAssignableFrom(implementationClass);
        } else {
            // 如果是类，检查继承关系
            return targetClass.isAssignableFrom(implementationClass) && !targetClass.equals(implementationClass);
        }
    }

    private Class<?> loadClass(String className) {
        try {
            return classLoader.loadClass(className);
        }
        // 此处不能 catch Exception，否则抓不到 NoClassDefFoundError，因为它是 Error 的子类
        catch (Throwable t) {
            Log.getLog(AopMappingPlugin.class).debug("AopMappingPlugin can not load the class \"" + className + "\"");

            /**
             * 由于扫描是一种主动行为，所以 pom.xml 中的 provided 依赖会在此被 loadClass，
             * 从而抛出 NoClassDefFoundError、UnsupportedClassVersionError、
             * ClassNotFoundException 异常。return null 跳过这些 class 不处理
             *
             * 如果这些异常并不是 provided 依赖的原因而引发，也会在后续实际用到它们时再次抛出异常，
             * 所以 return null 并不会错过这些异常
             */
            return null;
        }
    }

    /**
     * 支持路径中存在空格百分号等等字符
     */
    private String decodeUrl(String url) {
        try {
            return URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
    	AopMappingPlugin aopMappingPlugin = new AopMappingPlugin("com.tbynet.jwp.service.provider");
        aopMappingPlugin.start();
        aopMappingPlugin.stop();
    }

}
